# !/usr/bin/env python
# -*-coding:utf-8 -*-

"""
# File       : task_runner.py
# Time       ：2023/8/1 10:26
# Author     ：YangYong
# version    ：python 3.10
# Description：
    流程运行
    process running
"""
import json
import threading
import time
import traceback
import multiprocessing as mp
from queue import Empty
from typing import Any, List

from orderlines.execute.app_context import AppContext
from orderlines.execute.base_runner import BaseRunner
from orderlines.execute.running_check import CheckModule
from orderlines.execute.running_strategy import RunningStrategy
from orderlines.execute.states.process_finite_state_machines import ProcessInstanceState
from orderlines.execute.states.task_finite_state_machines import TaskInstanceState
from orderlines.execute.task_build import TaskBuild
from orderlines.utils.exceptions import OrderLineRunningException, OrderLineStopException, OrderLineTimeoutException
from orderlines.utils.orderlines_enum import TaskStates
from orderlines.utils.utils import get_method_param_annotation, default_task_config, get_task_config_item
from public.logger import logger


class TaskRunner(BaseRunner, threading.Thread):

    def __init__(self, process_instance_id: str, context: AppContext):
        BaseRunner.__init__(self, process_instance_id, context)
        threading.Thread.__init__(self)
        self.stop = False  # 是否停止
        self.paused = False  # 是否暂停
        self.running_db_operator = self.process_parse.task_parse.running_db_operator
        self.running_db_operator.process_instance_insert(self.process_info)
        self.task_fsm = TaskInstanceState(self.task_instance_id(), self.process_id, self.process_instance_id)
        self.process_fsm = ProcessInstanceState(self.process_instance_id)
        self.process_fsm.trigger_chance('start')

    def callback(self, task_status: str, result_or_error: dict) -> None:
        """
        on task run error callback func method
        @param task_status: task run status
        @param result_or_error: result or error
        @return:
        """
        task_config = self.process_parse.task_parse.task_config(self.current_task_id)
        notice_type = get_task_config_item(task_config, 'notice_type')
        callback_module = get_task_config_item(task_config, 'callback_module')
        callback_func = get_task_config_item(task_config, 'callback_func', 'send_msg')

        module_check = CheckModule()
        module_check.check_module(callback_module)
        callback_class = module_check.modules.get(callback_module)
        if task_status in notice_type:
            method = getattr(callback_class(), callback_func)
            flag, annotation = get_method_param_annotation(method)
            callback_param = {
                'process_name': self.process_name,
                'node_info': self.current_node(),
                'error_info': result_or_error,
                'status': task_status
            }
            logger.info(f'callback func is {callback_func}')
            callback_func_param = annotation(**callback_param)
            method(callback_func_param)

    def is_corner(self, running_task_instances: List[dict], current_task_id: str):
        """检查当前任务是否为独占任务"""
        task_instance = self.running_db_operator.get_current_task_instance(current_task_id)
        task_config = default_task_config(task_instance.get('task_config', {'is_corner': False}))
        for item in running_task_instances:
            is_same_method = item.get('method_name') == task_instance.get('method_name')
            is_same = not item.get('task_id') == current_task_id
            is_corner = task_config.get('is_corner')
            return is_same_method and is_same and is_corner

    def run(self) -> None:
        try:
            self.run_with_timeout()
        except Exception as error:
            logger.error(f'流程{self.process_name}运行异常:{error}。\n详细信息{traceback.format_exc()}')
            self.process_fsm.trigger_chance('failure', str(error))

    def run_with_timeout(self):
        """任务运行——带有超时"""
        process_params = json.loads(self.process_info.get('process_params')) or {}
        process_timeout = process_params.get('timeout') or 120
        try:
            # 获取正在运行中的其他任务实例，和当前将要运行的任务比较，判断是否为独占任务
            current_task_id = self.current_node().get('task_id')
            # 获取正在运行中的任务
            running_task_instances = self.running_db_operator.get_running_task_instance()
            flag = 0
            is_corner = self.is_corner(running_task_instances, current_task_id)
            if is_corner:
                logger.warn(f'当前任务::{current_task_id}为独占任务')
            while is_corner:
                if flag * 10 >= process_timeout:
                    raise OrderLineTimeoutException('流程超时，当前任务为独占任务等待其他任务等待超时')
                time.sleep(10)
                flag += 1
                is_corner = self.is_corner(running_task_instances, current_task_id)
                if flag % 10 == 0:
                    logger.info('等待独占任务中')

            self.task_run()
        except OrderLineTimeoutException:
            task_instance_id = self.current_node().get('task_instance_id')
            task_name = self.task_name()
            task_error_info = f'task_instance_id is {task_instance_id}, process timeout is {process_timeout}.' \
                              f' so task name 【{task_name}】 run timeout error, please update process timeout'

            logger.error(task_error_info, task_instance_id=self.task_instance_id())
            task_run_logs = logger.log_text(self.task_instance_id())
            self.task_fsm.trigger_chance('stop', task_run_logs)
            self.process_fsm.trigger_chance('stop')
            raise OrderLineTimeoutException(f'process name::{self.process_name} ,{task_error_info}')

    def task_run(self) -> None:
        """real task run"""
        # 流程状态改变为运行
        while self.is_complete and not self.stop:
            # 先将任务插入队列中
            self.process_parse.generate_task()
            if not self.paused:
                self.running_db_operator.task_instance_insert(self.current_node())
                self.task_fsm.trigger_chance('start')
                try:
                    # 解析变量，parse variable
                    self.on_running()
                except OrderLineStopException as error:
                    self.on_stop(error)
                except Exception as error:
                    self.on_failure(error)
                # 任务完成ack确认
                self.process_parse.task_parse.ack(self.message_id)

            self.stop, self.paused = self.running_db_operator.process_instance_is_stop_or_paused()

            if self.stop:
                task_instance_id = self.current_node().get('task_instance_id')
                error_log = f'process name {self.process_name} is stop, current task instance id {task_instance_id}'
                logger.info(error_log, task_instance_id=self.task_instance_id())
                task_run_logs = logger.log_text(self.task_instance_id())
                self.task_fsm.trigger_chance('stop', error_log, task_run_logs)
                break
            if not self.is_complete:
                logger.info(f'process name {self.process_name} run complete', task_instance_id=self.task_instance_id())
                break

            sleep_time = 10 if self.paused else 0.01

            if not self.paused:
                # if process not paused get next
                self.is_complete = self.process_parse.task_parse.is_complete()

            self.current_task_id = self.process_parse.consumer_task()
            if self.current_task_id:
                self.message_id = self.process_parse.message_id()
                self.task_fsm = TaskInstanceState(self.task_instance_id(), self.process_id, self.process_instance_id)
                logger.debug(
                    f'运行完成,下一个任务.\n'
                    f'任务ID:{self.current_task_id},实例ID:{self.task_instance_id()},message_id:{self.message_id}'
                )
                time.sleep(sleep_time)
            else:
                self.process_fsm.trigger_chance('success')

    def on_running(self) -> tuple:
        """
        任务运行时
        on task runtime
        @return:
            task_status: 任务状态
            result_or_error: 异常信息or任务结果
        """
        task_config = self.process_parse.task_parse.task_config(self.current_task_id)
        task_timeout = int(task_config.get('task_timeout'))
        task_name = self.task_name()
        try:
            if self.paused:
                return TaskStates.green.value, ''
            task_build = TaskBuild(self.process_instance_id, self.current_node())
            queue = mp.Queue()

            p = mp.Process(
                target=task_build.build,
                args=(self.current_task_id, self.process_info, self.task_nodes, queue)
            )
            p.start()
            p.join(timeout=task_timeout)
            result_or_error = queue.get(timeout=task_timeout)
            status = result_or_error.get('status')
            if status != TaskStates.green.value:
                raise OrderLineRunningException(result_or_error.get('error_info'))
            # 任务运行后置操作
            if status != TaskStates.green.value:
                self.callback(status, result_or_error)
            else:
                run_log = f'任务ID:{self.current_task_id},实例ID:{self.task_instance_id()},\n' \
                          f'任务名称:{task_name},任务结果:{result_or_error}'
                logger.info(run_log, task_instance_id=self.task_instance_id())
                task_run_logs = logger.log_text(self.task_instance_id())
                self.task_fsm.trigger_chance('success', result_or_error, task_run_logs)
        except Empty as error:
            raise OrderLineTimeoutException(
                f'【{task_name}】运行超时，任务给定超时时间为{task_timeout}. \n信息异常::{error}\n{traceback.format_exc()}')
        except Exception as error:
            raise OrderLineRunningException(f'【{task_name}】运行任务失败. \n信息异常::{error}\n{traceback.format_exc()}')

    def on_stop(self, error: Any):
        """
        on task stop
        @param error: error info
        @return:
            task_status: 任务状态
            error_info: 异常信息
        """
        error_log = f'任务ID:{self.current_task_id},实例ID:{self.task_instance_id()}运行超时.' \
                    f'\n异常信息:{error}\n详细信息:{traceback.format_exc()}'
        logger.error(error_log, task_instance_id=self.task_instance_id())
        task_run_logs = logger.log_text(self.task_instance_id())
        self.task_fsm.trigger_chance('stop', str(error), task_run_logs)
        self.process_fsm.trigger_chance('stop', str(error))
        self.is_complete = False

    def on_failure(self, error: Any):
        """
        on task failure
        @param error: task error info
        @return:
            task_status: 任务状态
            error_info: 异常信息
        """
        if isinstance(error, OrderLineTimeoutException):
            error_info = {'error_info': 'The task has timeout. Check timeout in task config'}
        else:
            error_info = {'error_info': traceback.format_stack()}

        error_log = f'任务ID:{self.current_task_id},实例ID:{self.task_instance_id()}运行失败.' \
                    f'\n异常信息:{error}\n详细信息{traceback.format_exc()}'
        logger.error(error_log, task_instance_id=self.task_instance_id())
        task_build = TaskBuild(self.process_instance_id, self.current_node())
        running_strategy = RunningStrategy(
            process_instance_id=self.process_instance_id,
            context=self.context,
            current_task_id=self.current_task_id,
            process_parse=self.process_parse,
            error_info=error_info,
            task_build=task_build,
            task_fsm=self.task_fsm,
            process_fsm=self.process_fsm,
            task_instance_id=self.task_instance_id()
        )
        self.is_complete = running_strategy.handle_strategy()
